﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace GameLibrary.Player
{
    using Settlement;
    using Unit;

    [Serializable]
    public sealed class HumPlayer:Player
    {
        #region variables

        public int TotalUnits
        {
            get
            {
                return this.units.Count;
            }
        }

        public int TotalSettlements
        {
            get
            {
                return this.settlements.Count;
            }
        }

        /// <summary>
        /// Store human player's templates
        /// </summary>
        private List<Template> TemplateList;

        public IEnumerable<Template> Templates
        {
            get { return TemplateList; }
        }

        #endregion

        #region constructor

        public HumPlayer(
            GameManager gm,
            Position pos,
            Bias bias,
            int initUnit = 2,
            int initCity = 1)
            : base(gm, pos, bias, initUnit, initCity)
        {
            this.InitTemplateList();
        }

        private void InitTemplateList()
        {
            this.TemplateList = new List<Template>() { };
            this.TemplateList.Add(Template.DefaultLabor);
            this.TemplateList.Add(Template.DefaultScout);
            this.TemplateList.Add(Template.DefaultSoldier);
            this.TemplateList.Add(Template.AllActions);
        }
        #endregion

        #region add

        public void AddCity(Position pos, SettlementFlags type)
        {
            Settlement city = new Settlement(pos, this.ID, type, this.Map.GetTileType(pos).BaseProduction);
            
            this.Map.AddSettlement(city);
            this.settlements.Add(city.ID, city);
        }

        public void AddUnit(Position pos, Template template)
        {
            Unit unit = new Unit(pos, this.ID, template);
            this.Map.AddUnit(unit);
            this.units.Add(unit.ID,unit);
        }

        public void AddTemplate(Template template)
        {
            this.TemplateList.Add(template);
        }

        #endregion

        #region kill 

        public void KillUnit(Unit unit)
        {
            this.OnMapObjectKilled(unit);
        }

        #endregion

        #region next turn

        internal void StartNextTurn()
        {
            this.NextTurn();
        }

        //refresh all map object
        // get income
        internal override void NextTurn()
        {
            this.SettlementNextTurn();
            this.UnitNextTurn();
            this.RefreshAllRegionsWithCity();
        }

        #endregion

        #region get       

        public SettlementInfo GetSettlementInfoByPosition(Position pos)
        {
            try
            {
                return this.Map.GetSettlementInfoByPosition(pos);
            }
            catch (KeyNotFoundException)
            {
                return null;
            }
            catch (NullReferenceException)
            {
                return null;
            }
        }

        public IEnumerable<UnitInfo> GetUnitInfosByPosition(Position pos)
        {
            try
            {
                return this.Map.GetUnitInfosByPosition(pos);
            }
            catch (KeyNotFoundException)
            {
                return new List<UnitInfo>();
            }
            catch (NullReferenceException)
            {
                return new List<UnitInfo>();
            }
        }

        public Tile.TileType GetTileByPosition(Position pos)
        {
            var tile = this.Map.GetTileType(pos);
            return tile;
        }

        public double[,] GetCostMap(Position pos)
        {
            var map = this.Map.GetCostMap(this.Map.GetRegionInfo(pos),null);
            if (map == null)
                throw new ArgumentNullException("cost map null");
            else
                return map;
        }

        public IEnumerable<UnitInfo> AllUnits()
        {
            return from unit in this.units.Values
                   select (unit as Unit).Info;
        }

        public IEnumerable<SettlementInfo> AllSettlements()
        {
            return from settlement in this.settlements.Values
                   select (settlement as Settlement).Info;
        }

        public Dictionary<uint,Dictionary<uint, DiplomaticRelationshipType>> GetRelations()
        {
            return GameManager.relationshipList;
        }

        private bool KnownThisPlayer(uint id)
        {
            return this.relationships.ContainsKey(id);
        }

        private Dictionary<uint,DiplomaticRelationshipType>
            GetKnownRelationshipOfAPlayer(
            uint id, 
            Dictionary<uint, DiplomaticRelationshipType> relationshipOfID)
        {
             var knowRelationship = from party in relationshipOfID
                                    where this.relationships.ContainsKey(party.Key)
                                    select party;
            
            Dictionary<uint, DiplomaticRelationshipType> relationDic = 
                        new Dictionary<uint,DiplomaticRelationshipType>();
            
            foreach(var kv in knowRelationship)
                        relationDic.Add(kv.Key,kv.Value);

            return relationDic;
        }
            
        #endregion

        #region unit actions

        #region fortify
        public void FortifyUnit(UnitInfo unitInfo)
        {
            double defenceEffect = this.GetDefenceEffect(unitInfo.Location);
            Unit unit = this.units[unitInfo.ID];
            this.Fortify(unit, defenceEffect);
        }

        private double GetDefenceEffect(Position pos)
        {
            return this.Map.GetTileType(pos).DefenceEffect;
        }

        private void Fortify(Unit unit, double defenceEffect)
        {
            unit.Current.Defence = (uint) (unit.Current.Defence
                                           * defenceEffect
                                           * unit.Current.AP );
            unit.Current.AP = 0;
            this.Map.RefreshUnit(unit);
        }
        #endregion

        #region build settlement
        public void UnitBuildSettlement(UnitInfo unit, SettlementFlags settlementType)
        {
            this.AddCity(unit.Location, settlementType);
            Unit u = this.GetUnit(unit);
            u.Current.AP -= 1;
            this.Map.RefreshUnit(u);
        }

        #endregion

        #region disband

        public void DisbandUnit(UnitInfo unit)
        {
            this.RemoveUnit(this.units[unit.ID] as Unit);
        }

        #endregion

        #region explore

        public void Explore(UnitInfo unit, Position pos)
        {
            this.Map.GetRegionInfo(pos);
            Unit u = this.units[unit.ID] as Unit;
            u.Current.AP = 0;
            this.Map.RefreshUnit(u);
        }

        #endregion

        #region upgrade

        public bool UpgradeUnit(UnitInfo unit, Template template)
        {
            if (IsBudgetAllows(template.Cost))
            {
                Unit u = this.GetUnit(unit);
                this.ResetUnitTemplate(u, template);
                this.Map.RefreshUnit(u);
                return true;
            }
            else
                return false;
        }

        private Unit GetUnit(UnitInfo unit)
        {
            return this.units[unit.ID] as Unit;
        }

        private void ResetUnitTemplate(Unit unit, Template template)
        {
            unit.UpgradeUnit(template);
        }
      
        #endregion

        #region move

        public void MoveUnitToPosition(UnitInfo unit, Position to)
        {
            Unit u = this.GetUnit(unit);
            this.MoveUnit(u, to);
            this.Map.RefreshUnit(u);
        }

        #endregion

        #region attack

        public bool UnitAttack(UnitInfo unitA, Position pos)
        {
            UnitInfo unitB = this.GetFirstUnitAtPosition(pos);
            if (unitB == null)
                return true;
            else
            {
                if (IsUnitAWin(unitA, unitB))
                {
                    var b = this.Map.GetUnitByID(unitB.ID);                    
                    OnMapObjectKilled(b);
                    this.Map.RefreshRegion(unitB.Location);
                    Unit a = this.GetUnit(unitA);
                    a.Current.AP--;
                    this.Map.RefreshUnit(a);
                    return true;
                }
                else
                {
                    this.RemoveUnit(this.Map.GetUnitByID(unitA.ID));
                    return false;
                }
            }
        }

        private bool IsUnitAWin(UnitInfo unitA, UnitInfo unitB)
        {
            float HPA, HPB;
            AI.FightSimulator.GetResult(unitA, unitB, out HPA, out HPB);
            if (HPB <= 0)
                return true;
            else
                return false;
        }

        private UnitInfo GetFirstUnitAtPosition(Position pos)
        {
            var units = this.Map.GetUnitInfosByPosition(pos);
            return units.First();
        }

        #endregion

        #endregion

        #region settlement actions

        public void OccupySettlement(SettlementInfo settlementInfo)
        {
            var settlement = this.Map.GetSettlementByID(settlementInfo.ID);
            this.ConnectEvents(settlement);
            settlement.SetOwnerID(this.ID);
            this.Map.RefreshRegion(settlement.Location);
        }

        public void AbandonSettlement(SettlementInfo settlement)
        {
            this.Map.RemoveSettlement(this.settlements[settlement.ID] as Settlement);
            this.settlements.Remove(settlement.ID);            
        }

        public bool BuildBuilding(SettlementInfo settlementInfo, BuildingType building)
        {
            Settlement settlement = this.GetSettlement(settlementInfo);
            int cost = this.GetCost(settlement, building);

            if (IsBudgetAllows(cost))
            {
                this.SettlementBuildBuilding(settlement, building);
                this.Map.RefreshSettlement(settlement);
                return true;
            }
            else
                return false;
        }

        private Settlement GetSettlement(SettlementInfo info)
        {
            return this.settlements[info.ID] as Settlement;
        }
      
        private int GetCost(Settlement settlement, BuildingType building)
        {
            return settlement.GetNextLevelBuildingCost(building);
        }
        
        private bool IsBudgetAllows(double cost)
        {
            if (base.Storage < cost)
                return false;
            else
                return true;
        }

        private void SettlementBuildBuilding(Settlement settlement, BuildingType building)
        {
            settlement.AddBuilding(building);
            this.Map.RefreshSettlement(settlement);
        }

        public void SettlementChangeType(SettlementInfo settlementInfo)
        {
            Settlement settlement = this.GetSettlement(settlementInfo);
            this.ChangeSettlementType(settlement);
        }

        private void ChangeSettlementType(Settlement settlement)
        {
            // only two type in v0.3
            if (settlement.Type == SettlementFlags.Village)
                settlement.ChangeToAnotherType(SettlementFlags.City);
            else
                settlement.ChangeToAnotherType(SettlementFlags.Village);
            this.Map.RefreshSettlement(settlement);
        }

        public bool BuildNewUnit(SettlementInfo settlement, Template template)
        {
            if (IsBudgetAllows(template.Cost))
            {
                this.AddUnit(settlement.Location, template);
                return true;
            }
            else
                return false;
        }

        public bool LinkToCity(SettlementInfo cityInfo, SettlementInfo villageInfo)
        {
            if (CheckCityIsCityAndVillageIsVillage(cityInfo, villageInfo))
            {
                Settlement village = this.settlements[villageInfo.ID] as Settlement;
                this.RemoveVillageFromCity(village);
                Settlement city = this.settlements[cityInfo.ID] as Settlement;
                city.AddVillage(village);
                this.Map.RefreshSettlement(village);
                this.Map.RefreshSettlement(city);
                return true;
            }
            else
                return false;
        }

        private bool CheckCityIsCityAndVillageIsVillage(SettlementInfo city,
                                                        SettlementInfo village)
        {
            if (city.Type == SettlementFlags.City
                && village.Type == SettlementFlags.Village)
                return true;
            else
                return false;
        }

        private void RemoveVillageFromCity(Settlement village)
        {
            uint id = village.SubordinateOf;
            if (village.SubordinateOf == 0
                || village.SubordinateOf == this.ID)
            {
                village.SetSubordinateOf(this.ID);
            }

            else
            {
                Settlement city = this.settlements[village.SubordinateOf] as Settlement;
                city.RemoveVillage(village);
                village.SetSubordinateOf(this.ID);
            }            
        }
        
        #endregion

        #region relationships

        public void DeclearWarOnPlayer(uint id)
        {
            this.OnDeclearWar(id);
        }

        #endregion
    }
}
